// Thud - a dynamic object-oriented language
// Copyright (C) 2009,2010 David H. Hovemeyer <david.hovemeyer@gmail.com>

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

// Abstract Syntax Tree Node class

#include <cassert>
#include "fe/SemanticAnalysisException.h"
#include "fe/IASTNodeVisitor.h"
#include "fe/Token2.h"
#include "fe/ASTNode.h"

ASTNode::ASTNode(ASTNodeType type, const Location &loc)
	: m_type(type)
	, m_loc(loc)
{
}

ASTNode::~ASTNode()
{
}

ASTNode *ASTNode::alloc(ASTNodeType type, const Location &loc)
{
	return new ASTNode(type, loc);
}

const Location &ASTNode::getLocation() const {
	return m_loc;
}

void ASTNode::setLocation(const Location &loc) {
	m_loc = loc;
}

void ASTNode::setFlag(ASTNodeFlag flag)
{
	m_flags |= (1 << flag);
}

bool ASTNode::isFlagSet(ASTNodeFlag flag) const
{
	int f = (1 << flag);
	return (m_flags & f) == f;
}

ASTNodeType ASTNode::getType() const
{
	return m_type;
}

void ASTNode::setStringValue(const std::wstring &sval)
{
	setFlag(ASTFLAG_HAS_STRING_VALUE);
	m_sval = sval;
}

void ASTNode::setStringValueFromToken(Token2 *t)
{
	setFlag(ASTFLAG_HAS_STRING_VALUE);
	m_sval = t->getLexeme();
}

void ASTNode::setStringValueFromStringLiteral(Token2 *t)
{
	setFlag(ASTFLAG_HAS_STRING_VALUE);
	const std::wstring &lexeme = t->getLexeme();

	for (std::wstring::const_iterator i = lexeme.begin(); i != lexeme.end(); i++) {
		if (i == lexeme.begin() || (i + 1) == lexeme.end()) {
			continue; // ignore beginning and ending double quote characters
		}

		if (*i == L'\\') {
			// escape sequence
			i++;
			switch (*i) {
			case L'n': m_sval += L'\n'; break;
			case L't': m_sval += L'\t'; break;
			case L'f': m_sval += L'\f'; break;
			case L'b': m_sval += L'\b'; break;
			case L'\\': m_sval += L'\\'; break;
			case L'"': m_sval += L'"'; break;
			default:
				throw SemanticAnalysisException(L"Illegal escape sequence in string literal: " + std::wstring(1, *i), m_loc);
			}
		} else {
			m_sval += *i;
		}
	}
}
	
const std::wstring &ASTNode::getStringValue() const
{
	assert(isFlagSet(ASTFLAG_HAS_STRING_VALUE));
	return m_sval;
}

void ASTNode::setIntValue(int32_t ival)
{
	setFlag(ASTFLAG_HAS_INT_VALUE);
	m_ival = ival;
}
	
int32_t ASTNode::getIntValue() const
{
	assert(isFlagSet(ASTFLAG_HAS_INT_VALUE));
	return m_ival;
}
	
void ASTNode::adoptImport(Import *import)
{
	setFlag(ASTFLAG_HAS_IMPORT);
	m_import = import;
}
	
const Import *ASTNode::getImport() const
{
	assert(isFlagSet(ASTFLAG_HAS_IMPORT));
	return m_import;
}

void ASTNode::prependChild(ASTNode *child)
{
	// New leftmost child: update line number
	m_loc = child->getLocation();

	m_childList.push_front(child);
}

void ASTNode::appendChild(ASTNode *child)
{
	m_childList.push_back(child);
}

ASTNode::ChildList& ASTNode::getChildList()
{
	return m_childList;
}

bool ASTNode::isExecutableStatement() const
{
	switch (m_type) {
	case AST_STATEMENT:
	case AST_IF_STATEMENT:
	case AST_RETURN_STATEMENT:
	case AST_WHILE_STATEMENT:
		return true;
	case AST_VAR_LIST: // see if any of the variable declarations have initializers
		for (ChildList::const_iterator i = m_childList.begin(); i != m_childList.end(); i++) {
			if ((*i)->isExecutableStatement()) {
				return true;
			}
		}
		return false;
	case AST_VAR:
		return !m_childList.empty(); // The var decl could have an initializer
	default:
		return false;
	}
}

void ASTNode::accept(IASTNodeVisitor *visitor)
{
	switch (m_type) {
	case AST_UNIT:
		visitor->visitUnit(this);
		break;
	case AST_IMPORT_LIST:
		visitor->visitImportList(this);
		break;
	case AST_IMPORT:
		visitor->visitImport(this);
		break;
	case AST_STATIC_IMPORT:
		visitor->visitStaticImport(this);
		break;
	case AST_STATEMENT_LIST:
		visitor->visitStatementList(this);
		break;
	case AST_STATEMENT:
		visitor->visitStatement(this);
		break;
	case AST_BINARY_OP_EXPR:
		visitor->visitBinaryOpExpr(this);
		break;
	case AST_UNARY_OP_EXPRESSION:
		visitor->visitUnaryOpExpr(this);
		break;
	case AST_FIELD_REF_EXPR:
		visitor->visitFieldRefExpr(this);
		break;
	case AST_METHOD_CALL_EXPR:
		visitor->visitMethodCallExpr(this);
		break;
	case AST_MODULE_METHOD_CALL_EXPR:
		visitor->visitModuleMethodCallExpr(this);
		break;
	case AST_VAR_REF_EXPR:
		visitor->visitVarRefExpr(this);
		break;
	case AST_INT_LITERAL_EXPR:
		visitor->visitIntLiteralExpr(this);
		break;
	case AST_BOOLEAN_LITERAL_EXPR:
		visitor->visitBooleanLiteralExpr(this);
		break;
	case AST_SYMBOL_LITERAL_EXPR:
		visitor->visitSymbolLiteralExpr(this);
		break;
	case AST_STRING_LITERAL_EXPR:
		visitor->visitStringLiteralExpr(this);
		break;
	case AST_SELF_REF_EXPR:
		visitor->visitSelfRefExpr(this);
		break;
	case AST_NEW_EXPR:
		visitor->visitNewExpr(this);
		break;
	case AST_NIL_EXPR:
		visitor->visitNilExpr(this);
		break;
	case AST_CLASS:
		visitor->visitClass(this);
		break;
	case AST_MODULE:
		visitor->visitModule(this);
		break;
	case AST_MEMBER_LIST:
		visitor->visitMemberList(this);
		break;
	case AST_METHOD:
		visitor->visitMethod(this);
		break;
	case AST_MAGIC_METHOD:
		visitor->visitMagicMethod(this);
		break;
	case AST_FIELD:
		visitor->visitField(this);
		break;
	case AST_VAR_LIST:
		visitor->visitVarList(this);
		break;
	case AST_VAR:
		visitor->visitVar(this);
		break;
	case AST_ARG_LIST:
		visitor->visitArgList(this);
		break;
	case AST_IF_STATEMENT:
		visitor->visitIfStatement(this);
		break;
	case AST_WHILE_STATEMENT:
		visitor->visitWhileStatement(this);
		break;
	case AST_RETURN_STATEMENT:
		visitor->visitReturnStatement(this);
		break;
	default:
		assert(false);
	}
}

void ASTNode::destroy(ASTNode *ast)
{
	for (std::deque<ASTNode *>::iterator i = ast->m_childList.begin(); i != ast->m_childList.end(); i++) {
		destroy(*i);
	}

	if (ast->isFlagSet(ASTFLAG_HAS_IMPORT)) {
		Import::destroy(ast->m_import);
	}

	delete ast;
}
